'use strict';

RMModule.factory('RMBuilderExt', ['$injector', '$parse', 'inflector', '$log', 'restmod', function($injector, $parse, inflector, $log, restmod) {

  var bind = angular.bind,
      isFunction = angular.isFunction;

  /**
   * @class ExtendedBuilderApi
   *
   * @description
   *
   * Non-core builder extensions
   *
   * Adds the following property modifiers:
   * * `serialize` sets the encoder and decoder beaviour for an attribute, maps to {@link BuilderApi#attrSerializer}
   *
   */
  var EXT = {
    /**
     * @memberof ExtendedBuilderApi#
     *
     * @description Sets an url prefix to be added to every url generated by the model.
     *
     * This applies even to objects generated by the `$single` method.
     *
     * This method is intended to be used in a base model mixin so everymodel that extends from it
     * gets the same url prefix.
     *
     * Usage:
     *
     * ```javascript
     * var BaseModel = restmod.mixin(function() {
     *   this.setUrlPrefix('/api');
     * })
     *
     * var bike = restmod.model('/bikes', BaseModel).$build({ id: 1 });
     * console.log(bike.$url()) // outputs '/api/bikes/1'
     * ```
     *
     * @param {string} _prefix url portion
     * @return {BuilderApi} self
     */
    setUrlPrefix: function(_prefix) {
      return this.setProperty('urlPrefix', _prefix);
    },

    /**
     * @memberof ExtendedBuilderApi#
     *
     * @description Changes the model's primary key.
     *
     * Primary keys are passed to scope's url methods to generate urls. The default primary key is 'id'.
     *
     * **ATTENTION** Primary keys are extracted from raw data, so _key must use raw api naming.
     *
     * @param {string|function} _key New primary key.
     * @return {BuilderApi} self
     */
    setPrimaryKey: function(_key) {
      return this.setProperty('primaryKey', _key);
    },

    /**
     * @memberof ExtendedBuilderApi#
     *
     * @description Assigns a serializer to a given attribute.
     *
     * A _serializer is:
     * * an object that defines both a `decode` and a `encode` method
     * * a function that when called returns an object that matches the above description.
     * * a string that represents an injectable that matches any of the above descriptions.
     *
     * @param {string} _name Attribute name
     * @param {string|object|function} _serializer The serializer
     * @return {BuilderApi} self
     */
    attrSerializer: function(_name, _serializer, _opt) {
      if(typeof _serializer === 'string') {
        _serializer = $injector.get(inflector.camelize(_serializer, true) + 'Serializer');
      }

      if(isFunction(_serializer)) _serializer = _serializer(_opt);
      if(_serializer.decode) this.attrDecoder(_name, bind(_serializer, _serializer.decode));
      if(_serializer.encode) this.attrEncoder(_name, bind(_serializer, _serializer.encode));
      return this;
    },

    /// Experimental modifiers

    /**
     * @memberof ExtendedBuilderApi#
     *
     * @description Expression attributes are evaluated every time new data is fed to the model.
     *
     * @param {string}  _name Attribute name
     * @param {string} _expr Angular expression to evaluate
     * @return {BuilderApi} self
     */
    attrExpression: function(_name, _expr) {
      var filter = $parse(_expr);
      return this.on('after-feed', function() {
        this[_name] = filter(this);
      });
    }
  };

  return restmod.mixin(function() {
    this.extend('setUrlPrefix', EXT.setUrlPrefix)
        .extend('setPrimaryKey', EXT.setPrimaryKey)
        .extend('attrSerializer', EXT.attrSerializer, ['serialize']);
  });
}]);